#pragma once

#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <openssl/ssl.h>

#define NEW_LINE_SYMBOL '\n'
#define END_SYMBOL '\0'

struct IoResult
{
    uint32_t bRes : 1;
    uint32_t bEnd : 1;
    uint32_t bErr : 1;
    uint32_t nLen : 29;
};

struct BufBlock
{
    uint8_t *pBuf;
    uint32_t nEnd;
    uint32_t nRead;
    uint32_t nWrite;

    BufBlock()
    {
        pBuf = nullptr;
        nEnd = nRead = nWrite = 0;
    }

    int ReadableLen() { return (nWrite - nRead); }
    int WritableLen() { return (nEnd - nWrite); }

    IoResult Read(void *pDest, int iLen)
    {
        IoResult res = {0};
        int iReadable = nWrite - nRead;
        bool bEnough = (iReadable >= iLen);
        int iValid = (bEnough ? iLen : iReadable);

        memcpy(pDest, pBuf + nRead, iValid);
        nRead += iValid;

        if (nRead >= nEnd)
            res.bEnd = true;

        res.nLen = iValid;
        res.bRes = (bEnough || res.bEnd);

        return res;
    }

    IoResult ReadLine(void *pDest)
    {
        IoResult res = {0};
        uint8_t nChar = 0;

        while (true)
        {
            if (nRead >= nEnd)
            {
                res.bEnd = true;
                break;
            }

            if (nRead >= nWrite)
            {
                res.bErr = true;
                break;
            }

            nChar = *(pBuf + nRead);
            *(uint8_t *)pDest = nChar;
            pDest = (uint8_t *)pDest + 1;
            res.nLen++;
            nRead++;

            if (NEW_LINE_SYMBOL == nChar || END_SYMBOL == nChar)
            {
                res.bRes = true;
                break;
            }
        }

        return res;
    }

    IoResult Write(void *pSrc, int iLen)
    {
        IoResult res = {0};
        int iWritable = nEnd - nWrite;
        bool bEnough = (iWritable >= iLen);
        int iValid = (bEnough ? iLen : iWritable);

        memcpy(pBuf + nWrite, pSrc, iValid);
        nWrite += iValid;

        if (nWrite >= nEnd)
            res.bEnd = true;

        res.nLen = iValid;
        res.bRes = (bEnough || res.bEnd);

        return res;
    }

    IoResult Recv(int ifd, SSL *pssl)
    {
        IoResult res = {0};
        int iWritable = 0;
        int iRet = 0;

        while (true)
        {
            iWritable = nEnd - nWrite;

            if (nullptr == pssl)
                iRet = read(ifd, pBuf + nWrite, iWritable);
            else
                iRet = SSL_read(pssl, pBuf + nWrite, iWritable);

            if (0 < iRet)
            {
                nWrite += iRet;
                res.nLen += iRet;

                if (nWrite >= nEnd)
                {
                    res.bEnd = true;
                    break;
                }
            }
            else
            {
                res.bErr = true;

                if (-1 == iRet)
                {
                    if (EAGAIN == errno)
                    {
                        res.bRes = true;
                        res.bErr = false;
                    }
                    else if (EINTR == errno)
                    {
                        res.bErr = false;
                        continue;
                    }
                }

                break;
            }
        }

        return res;
    }

    IoResult Send(int ifd, SSL *pssl)
    {
        IoResult res;
        int iRet = 0;
        int iReadable = nWrite - nRead;
        res.bErr = true;

        while (0 < iReadable)
        {
            if (nullptr == pssl)
                iRet = write(ifd, pBuf + nRead, iReadable);
            else
                iRet = SSL_write(pssl, pBuf + nRead, iReadable);

            if (0 < iRet)
            {
                nRead += iRet;
                res.nLen += iRet;

                if (iRet >= iReadable)
                {
                    if (nRead >= nEnd)
                        res.bEnd = true;
                    else
                        res.bRes = true;

                    res.bErr = false;
                    break;
                }
            }
            else
            {
                if (-1 == iRet)
                {
                    if (EAGAIN == errno)
                    {
                        res.bRes = true;
                        res.bErr = false;
                    }
                    else if (EINTR == errno)
                    {
                        continue;
                    }
                }

                break;
            }

            iReadable = nWrite - nRead;
        }

        return res;
    }
};
